package gbench.sandbox.record;

import org.junit.jupiter.api.Test;

import java.time.LocalDateTime;
import java.util.*;
import gbench.common.fs.FileSystem;
import gbench.common.matlib.MatlibCanvas.NVec;
import gbench.common.matlib.MatlibCanvas.XVec;
import gbench.common.matlib.data.DataReader.DFrame;
import gbench.common.matlib.rdd.PVec;
import gbench.common.tree.LittleTree;
import gbench.common.tree.LittleTree.IRecord;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static gbench.common.matlib.MatlibCanvas.println;
import static gbench.common.tree.LittleTree.*;
import static gbench.common.tree.LittleTree.IRecord.*;
import static gbench.common.tree.LittleTree.KVPair.*;

public class JunitRecord {
    
    /**
     * 把IRecord视为trie,但是有个问题就是只能识别最长的字符串。
     */
    @Test
    public void foo_trie() {
        final var words= Stream.of("good,goodbye,hello,every,everyone".split(",")).map(e->KVP(L(e.split("")),e));
        final var rec = MAP2REC(KVPSMERGE2(words));
        rec.dfs_forone2(System.out::println);
    }
    
    /**
     * 一个IRecord 可以被视作一个 Node<String>节点。
     * /e/v/e/r/y/o/n/e --> everyone
     * /g/o/o/d/b/y/e --> goodbye
     * /h/e/l/l/o --> hello
     */
    @Test
    public void foo_tree() {
        final var recs = A(REC("a/b/c", 123, "a/b/d", 456, "a/e", 7), REC("f/g", 9, "h", 9, "i", L(1, 2, 3)));
        RECMERGE2(recs)
        .treeNode().forEach(node->{
            System.out.println("\t".repeat(node.getLevel()-1)+node.getPath()+"--->"+node.props());
        });
        
        System.out.println("\ntreeNode 会把kvp 的key都视为 子节点名 "); 
        recs[0]
        .treeNode().forEach(node->{
            System.out.println("\t".repeat(node.getLevel()-1)+node.getPath()+"--->"+node.props());
        });
    }
    
    /**
     * 增加 foo_rec3
     */
    @Test
    public void foo_rec3() {
        final var rec = RECMERGE2(REC("a", 4, "a/b/c", 234), REC("a/b/d", 567), REC("a/b/d", 567,"a",29));
        rec.dfs_forone2(System.out::println);
        System.out.println("键值对儿 的 合并");
        System.out.println(rec);
        
        System.out.println("深层次 会 覆盖 浅层次");
        System.out.println(RECMERGE2(REC("a", 4), REC("a/b/d", 567)));
        System.out.println("同(名)层次的 数据会 被合并成 一个 集合");
        System.out.println(RECMERGE2(REC("a", 4), REC("a/", 8)));
    }
    
    /**
     * 层级结构展示
     */
    @Test
    public void bar_rec3(){
        System.out.println("路径结构的 IRecord 会分解成层级结构的 IRecord,连个同名key 'g' 会被合并成一个 List结构");
        REC3(REC("a/b/c", 123, "a/b/d", 456), REC("e/f", 7, "g", 8,"h",L(1,2,3)),REC("g",REC("hello",9))).dfs_forall((p, v) -> {
            System.out.println(p + "--->" + v);
        });
    }
    
    /**
     * 层级结构展示
     */
    @Test
    public void bar_rec3_tree(){
        System.out.println("路径结构的 IRecord 会分解成层级结构的 IRecord");
        REC3(REC("a/b/c", 123, "a/b/d", 456), REC("e/f", 7, "g", 8,"h",L(1,2,3))).dfs_forall((p, v) -> {
            System.out.println(p + "--->" + v);
        });
        
        System.out.println("\n单值节点a会被湖面的复合节点(a/b/c) 给覆盖掉 ");
        REC3(REC("a",123,"a/b/c",234)).dfs_forone((p, v) -> {
            System.out.println(p + "--->" + v );
        });
        
        System.out.println("\n单值节点a会被后面的复合节点(a/b/c) 给覆盖掉,值的顺序无关紧要，复合一定会覆盖单值 ");
        REC3(REC("a/b/c",234,"a",123)).dfs_forone((p, v) -> {
            System.out.println(p + "--->" + v );
        });
        
        System.out.println("\n使用CallBack对象来进行处理");
        final var rec = REC3(REC("a/b/c", 123, "a/b/d", 456), REC("e/f", 7, "g", 8,"h",L(1,2,3)));
        final var root = new Node<String>("root");// 生成根节点
        final var mm = new HashMap<List<String>,Node<String>>();// 树形结构的拼接缓存
        
        // 生成callback 对象
        class CallBack implements BiConsumer<String, Object> {
            /**
             * 构造函数
             * @param callback
             * @param parentPath
             */
            public CallBack(BiConsumer<List<String>, Object> callback,List<String>parentPath){
                this.callback = callback;
                this.parentPath = parentPath.stream().collect(Collectors.toList());
            }
            
            /**
             * 构造函数
             * @param callback
             */
            public CallBack(BiConsumer<List<String>, Object> callback){
                this(callback,new LinkedList<String>());
            }
            
            @Override
            public void accept(final String key, final Object value) {
                final var node_path = Stream.concat(this.parentPath.stream(), Stream.of(key)).collect(Collectors.toList());// 拼接成新的路径
                final var node = mm.computeIfAbsent(node_path, k->new Node<String>(key));
                final var parentNode = mm.compute(this.parentPath, (k,v)->v==null?root:v);
                if(parentNode!=node)parentNode.addChild(node);
                
                if (value instanceof IRecord) {
                    final var cb = new CallBack(callback,node_path);
                    ((IRecord)value).foreach(cb);
                } else {
                    this.callback.accept(node_path, value);
                }// if
            }
            
            private final BiConsumer<List<String>, Object> callback;// 回调函数
            private final List<String> parentPath;// 上级节点的path
        }
        
        // 结构遍历
        rec.foreach(new CallBack((path,value)->System.out.println(path+"-->"+value)));
        
        System.out.println("\n把一个IRecord转换成一个LittleTree的Node");
        // LittleTree 方式的节点遍历
        root.forEach(e->{
            System.out.println("\t".repeat(e.getLevel()-1)+""+e.getPath());
        });
        
    };

    
    @Test
    void boo() {
       System.out.println(A2REC(A(1,2,3)).add(A2REC(A(4,5,6))));// 用4,5,6 替换掉原来的1,2,3
    }
    
    @Test
    public void foo0() {// lla 的演示，可以自动转换列表
        System.out.println(REC("a","1").lla("a", e->e+""));// 成功放回列表,单值也可以返回列表
        System.out.println(REC("a",A(1,2,3)).lla("a", e->e+""));// 成功放回列表
        System.out.println(REC("a",L(1,2,3)).lla("a", e->e+""));// 成功放回列表
        System.out.println(REC("a","1").lla("not_exists_key", e->e+""));
    }
    
    @Test
    public void foo(){
        final var rec = REC();
        rec.add("name",null);
        rec.add("keys",null);
        rec.set("address", (Object) null); // 如果吧强制转换会调用 set(String key, Function<Object, Object> mapper)
        rec.set("address", null); // set(String key, Function<Object, Object> mapper)
        System.out.println(rec);
        
        final var mm = new HashMap<String,String>();
        mm.put("name",null);
        System.out.println(mm);
    }
    
    @Test
    public void bar() {
        System.out.println(IRecord.obj2target(1, java.sql.Timestamp.class));
        final var rec = REC("a",0,"b",1);
        final var rec2 = REC("a",2,"c",3);
        System.out.println("merge false ---> "+rec.merge(rec2,false));
        System.out.println("merge true 等同于 add ---> "+rec.merge(rec2,true));
        System.out.println("add"+rec.add(rec2));
    }
    
    
    /**
     * 触发类型转换异常
     */
    @Test
    public void qux() {
        final var thetas = REC(
            "layer0",A(1d, A(1d,1d,1d,1d,1d)),
            "layer2",A(2d, A(2d,2d,2d,2d,2d)),
            "layer3",A(3d, A(3d,3d,3d,3d,3d))
        );
        
        System.out.println("注意这里会触发一个异常！！！！");
        try {
            var aa = thetas.strs("layer0");
            System.out.println("aa:\n"+aa);//类型报错
        }catch(Exception e) {
            e.printStackTrace();
        }
        
        var bb = thetas.lla("layer0", e->e+"");
        System.out.println("bb:\n"+Arrays.asList(bb));
        
        var cc = thetas.dbls("layer0");
        cc[0]=1000d;// 修改cc 可以直接修改到thetas 中的数据。
        System.out.println("cc:\n"+Arrays.asList(cc));
        System.out.println("rec:\n"+thetas.toString2());
    }
    
    @Test
    public void bar2() {
        final var thetas = REC(
            "layer0", REC(
                    "n0", A(1d, A(1d,1d,1d,1d,1d)),
                    "n1", A(2d, A(1d,1d,1d,1d,1d)),
                    "n2", A(3d, A(1d,1d,1d,1d,1d))
            ),"layer1", REC(
                    "n0", A(1d, A(1d,1d,1d,1d,1d)),
                    "n1", A(2d, A(1d,1d,1d,1d,1d)),
                    "n2", A(3d, A(1d,1d,1d,1d,1d))
            )
        );
        thetas.path2dbls("layer0/n0")[0]=1000d;
        System.out.println(thetas.rec("layer0").toString2());
        System.out.println(thetas.rec("layer1").toString2());
    }
    
    @Test
    public void bar3() {
        final var ree = REC(0,new LittleTree.ExpressionEvaluator());// 表达式计算
        final var r = REC("a",1,"b",2,"c",3);
        final var v = ree.evalExpr("#a+#b*#c",r);
        System.out.println(v);
    }
    
    /**
     * 读取R的数据文件
     */
    @Test
    public void bar4() {
        final var ss =  FileSystem.localUtf8Lines("iris.csv",this.getClass());
        final var ar = new AtomicReference<String[]>();
        final var recs = new LinkedList<IRecord>();
        ss.forEach(e->{
            if(ar.get()==null) {
                ar.set(e.replace("\"","").split(","));
                return;
            }
            var rec = STRING2REC(e.replace("\"", "")).keys(ar.get());
            recs.add(rec);
        });
        
        var rec = IRecord.ROWS2COLS(recs);
        rec = rec.filter("Petal.Length,Petal.Width,Species");
        System.out.println(rec.toString2());
        
        final var r = REC(rec.aovs((List<Object>oo)->{// 元素分组
            final var grps = REC(GROUP(oo,e->e));// 元素分组
            final var size = oo.size();// 提取向量长度
            return grps.aovs((List<Object>uu)->uu.size()*1.0/size);// 计算分组的元素的频率
        }));
        
        r.dfs_forone((path,v)->{
            var pk = path.replaceFirst("/[^/]+/*$", "");
            var aa = r.path2rec(pk);
            System.out.println(pk+"     "+aa.size());
            System.out.println(MFT("{0}\t{1}",path,v));
        });
    }
    
    /**
     * 把一个IRecord行记录集合转换成IRecord对象
     */
    @Test
    public void bar5() {
        System.out.println(cph2(RPTA(3,A(1,2,3))).collect(rclc).toString2());
    }
    
    /**
     * columns 列类型的数据展示
     */
    @Test
    public void qux2() {
        REC("user1","name").columns2()
        .forEach(col->{// 单行记录也可以取IRecord
            System.out.println(col);
        });
        
        REC("user1",A("name")).columns2()
        .forEach(col->{// 单行记录也可以取IRecord
            System.out.println(col);
        });
        
        REC("user1",A("1","2",3)).columns2()
        .forEach(col->{// 单行记录也可以取IRecord
            System.out.println(col);
        });
    }
    
    /**
     * columns 列类型的数据展示
     */
    @Test
    public void qux3() {
        
        var aa = A(1,2,3,4,5);// Integer 类型
        var bb = A(1d,2l,3f,4);//Number 类型
        var cc = A("abc",1,REC("name","zhangsan","sex",true));//Serializable
        Object[] cc1 = A("abc",1,REC("name","zhangsan","sex",true));//Serializable 可以赋值给 Object[]
        var dd = A("abc",1,REC("name","zhangsan","sex",true),this);//Object 类型
        System.out.println(A2REC(aa));
        System.out.println(A2REC(bb));
        System.out.println(A2REC(cc));
        System.out.println(A2REC(cc1));
        System.out.println(A2REC(dd));
        
        REC("aa",A(1,2,3,4),"bb",A(1,2,3,4,5)).flatMap((Integer[]oo)->{
            return Stream.of(oo);
        }).forEach(System.out::println);
        
    }
    
    /**
     * 合并多个IRecord 为一个IRecord
     */
    @Test
    public void qux4() {
        final var rec = cph2(RPTA(3,A(1,2,3))).collect(rclc);
        System.out.println(rec.toString2());
    }
    
    /**
     * 启动
     */
    @Test
    public void qux6() {
        System.out.println("sliding(1,2,3,4,5,6),size:2,step:1");
        IRecord.sliding(L(1,2,3,4,5,6),2,1).forEach(e->{
            System.out.println(e);
        });
        
        System.out.println("\nsliding(1,2,3,4,5,6),size:3,step:3");
        IRecord.sliding(L(1,2,3,4,5,6),3,3).forEach(e->{
            System.out.println(e);
        });
    }
    
    /**
     * lisp 函数示例
     */
    @Test
    public void carcdr() {
        println("car",REC().car());
        final var rec = IRecord.A2R(1,2,3,4);
        println("car",rec.car());
        println("cdr",rec.cdr());
        println("cdr cdr",rec.cdr().cdr());
        println("cdr cdr cdr",rec.cdr().cdr().cdr());
        println("cdr cdr cdr cdr",rec.cdr().cdr().cdr().cdr());
        println("cdr cdr cdr cdr cdr",rec.cdr().cdr().cdr().cdr().cdr());
    }
    
    /**
     * 数据框的用法（随机生成)演示
     */
    @Test
    public void dfm() {
        final var rnd = new Random();// 随机值生成器
        final var recschema = REC( // 记录结构元数据
            "key", A("id", "name", "sex", "birth"), // 键名
            "value", A("编号", "姓名", "性别", "出生日期") // 键值
        ).rcollect2(rclc2);
        println("tableschema:",recschema);
        final var rb = IRecord.builder(recschema.keys()); // IRecord 构造器
        final var letters = XVec.OF("赵钱孙李周吴郑王冯陈褚卫蒋沈韩杨朱秦尤许何吕施张孔曹严华金魏陶姜戚谢邹喻柏水窦章".split(""));// 基础字符集合（源数据字符序列，取自百家姓）
        final Supplier<String> namegen = ()->letters.shuffle().head(3).collect(Collectors.joining());// 随机生成3个字符长度的名字
        final var users = Stream.iterate(0, i->i+1).limit(10) // 随机初始化数据项目
            .map(i->rb.get(i,namegen.get(),i%2==0,LocalDateTime.now().minusDays(rnd.nextInt(10000)))) //  准备数据项目
            .collect(rclc).keys(recschema.values(String.class)); // 生成DataFrame 并修改键名
        println("users:",users.toString2());
        println("users2:",users.rcollect(rclc).toString2());
    }
    
    /**
     * 其他函数
     */
    @Test
    public void gets() {
        println("flagbools false 2 3",IRecord.flagbools(false, 2,3).apply(5));
        println("flagbools true 2 3",IRecord.flagbools(true, 2,3).apply(5));
        final var rec = STRING2REC("苹果,西瓜,草莓,哈密瓜,鸭梨");// 水果列表 
        cph2(RPT(rec.size(),L(true,false))).map(e->rec.gets(e.bools())).map(e->MFT("{0}",e.values())) 
        .forEach(System.out::println); 
    }
    
    /**
     * 比较
     */
    @Test
    public void compare() {
        final var rec1 = REC("id",1,"name","张三","birth",LocalDateTime.now());
        final var rec2 = REC("id",2,"name","李四","birth",LocalDateTime.now().minusDays(500));
        final var rec3 = REC("name","王五","birth",LocalDateTime.now().minusDays(100));
        
        System.out.println("\n指定键名序列比较,逆序：");
        Stream.of(rec1,rec2,rec3).sorted(cmp(rec1.keys(),false)).forEach(System.out::println);
        System.out.println("\n默认比较：");
        Stream.of(rec1,rec2,rec3).sorted().forEach(System.out::println);
        System.out.println("\n指定比较 birth,id,name：");
        Stream.of(rec1,rec2,rec3).sorted(cmp("birth,id,name")).forEach(System.out::println);
        System.out.println("\n按照name比较：");
        Stream.of(rec1,rec2,rec3).sorted(cmp("name")).forEach(System.out::println);
    }
    
    /**
     * 嵌套型IRecord的路径（键名序列）式 值访问 演示
     */
    @Test
    public void path() {
        final var rec = REC("zhangsan",REC("name","zhangsan","age",34,"addresses",new String[] {"上海","北京","广州"})); // 张三的数据记录
        System.out.println("zhangsan/age pathS :"+rec.pathS("zhangsan/age", 34).collect(NVec.nveclc()));
        System.out.println("zhangsan/age pathclc:"+rec.pathclc("zhangsan/age",NVec.nveclc()));
        System.out.println("zhangsan/addresses pathL:"+rec.pathL("zhangsan/addresses"));
        System.out.println("zhangsan/age pathA:"+Arrays.deepToString(rec.pathA("zhangsan/age")));
        System.out.println("zhangsan/addresses pathA:"+Arrays.deepToString(rec.pathA("zhangsan/addresses")));
        System.out.println("zhangsan/age pathT:"+rec.pathT("zhangsan/age",34));
        System.out.println("不能存在的key值返回 默认值,zhangsan/age1 pathT:"+rec.pathT("zhangsan/age1",0));
        
        // pathO的演示
        final var zhangsan = REC("name",REC("firstName","zhang","secondName","san"));
        System.out.println("name pathO:"+zhangsan.pathO("name"));
        System.out.println("name/firstName pathO:"+zhangsan.pathO("name/firstName"));
        System.out.println("name/firstName:"+zhangsan.pathclc("name/firstName",XVec.xveclc()));
    }
    
    /**
     * IRecord与PVec之间的相互转换
     */
    @Test
    public void pvec() {
        final var zhangsan = REC("name","zhangsan","sex","male","address","shanghai changning district xinhua street");
        final var pvec = zhangsan.collect(PVec.pvec3clc(DFrame::kvp2bp));
        System.out.println("pvec:"+pvec);
        final var rec = pvec.map(DFrame::bp2kvp).collect(IRecord.rclc2);
        System.out.println("rec:"+rec);
    }

    /**
     * IRecord的equals的实现
     */
    @Test
    public void eql() {
        final var rec = LittleTree.SimpleRecord.REC2("a/b/c", 1, "a/b/c", 1); // 注意rec 的两个key是一样的,REC2表示可以进行重复键值
        println("keys",rec.keys());
        final var mm = rec.applyForKvs(IRecord::STRING2REC, e -> e);
        println("mm",mm);
        println("compareTo:", IRecord.STRING2REC("1/2").compareTo(IRecord.STRING2REC("1/2"))); // 0
        println("equals:", IRecord.STRING2REC("1/2").equals(IRecord.STRING2REC("1/2"))); // false
    }
    
	/**
	 * IRecord的不同类型比较
	 */
	@Test
	public void cmp2() {
		final var rec = REC("name", "book", "x", "123");
		final var rec1 = REC("name", "book", "x", 123);
		println("rec,rec1",rec, rec1);
		println("rec,rec1 equals",rec, rec1, rec.equals(rec1));
		final var a = REC("a", null);
		final var b = REC("a", "null");
		final var c = REC("a", null);
		println("a,b", a, b, a.equals(b));
		println("b,a", b, a, b.equals(a));
		println("a,c", a, c, a.equals(c));
	}
    
    /**
     * 实现了 equals 就必须实现hashcode 以保证 equals 相同时 hashcode 也相同。 因为 hashmap 是 按照
     * hashcode->equals 的顺序 进行 数据存贮&访问的。 hashcode 和 equals的测试
     */
    @Test
    public void hascode_equals() {
        final var rec = REC("name", 1, "path", "a");
        final var map = new HashMap<IRecord, IRecord>();
        map.put(rec, rec);
        map.put(rec.derive(), rec);

        println("rec.equals(dd.derive())", rec.equals(rec.derive()));
        println("map.get(dd.derive())", map.get(rec.derive()));
        println("map.get(dd)", map.get(rec));
        println("dd.hashCode(),dd.derive().hashCode()", rec.hashCode(), rec.derive().hashCode());
        map.forEach((k, v) -> {
            println(k, v);
        });

        final var r = REC(); // 定义一个r
        r.add("key", r); // 递归引用自己
        println("r.hashCode()", r.hashCode());
        println("r.toString()", r);
    }
    
    /**
     * IRecord 的数据拆分示例
     */
    @Test
    public void split() {
        final var rec = REC("name","张三","sex","男","birth",LocalDateTime.now(),"province","上海","city","上海","district","长宁区","street","法华镇路101弄"); // 源数据
        println("flatMap方法演示");
        rec.flatMap(selects("name,sex,birth","province,city,district,street"),e->e).forEach(e->{
            println(e);
        });
        
        println("---------------------\n按照索引序号进行分解:");
        rec.splitS(A(0,1,2),A(2,3,4,5,6)).forEach(e->{
            println(e);
        });
        
        println("---------------------\n拆分成列表：");
        println("索引拆分",rec.split(A(0,1,2),A(2,3,4,5,6)));
        println("键名拆分",rec.split("name,sex,birth","province,city,district,street"));
        
        println("---------------------\n结构化用户数据:");
        final var userinfo = rec.splitS("name,sex,birth","province,city,district,street").collect(IRecord.rbclc("profile,address"));
        final var userinfo2 = userinfo.dfs_eval_forone2((String path, Object value) -> KVPair.KVP(path, value)).collect(IRecord.rclc2);
        println("path key 结构化: \n",userinfo2);
        
        println("---------------------\n 结构化用户数据2, 采用 rbclc加工数据:");
        final var userinfo3 = rec.splitS("name,sex,birth","province,city,district,street").collect(IRecord.rbclc("profile,address",e->MFT("【{0}】",e)));
        println(userinfo3);
    }
    
    /**
     * IRecord.rbclc 的作用就是为了一个流中的每个元素（值数据）配置一个键名，进而构造出一个键值对儿集合,并把这个键值对儿集合 归并为IRecord. <br>
     * 对于 IRecord.rbclc 中指定了键名列表(定长键名列表)的情况，超出键名列表长度的元素会被剔除掉。
     */
    @Test
    public void rbclc() {
        final var data = IRecord.A(1, 2, 3, 4, 5); // 源数据
        println("默认名称", Stream.of(data).collect(IRecord.rbclc(e -> "-".repeat(e))));
        println("默认名称", Stream.of(data).collect(IRecord.rbclc((String) null, e -> "-".repeat(e))));
        println("指定名称", Stream.of(data).collect(IRecord.rbclc("红橙黄绿蓝".split(""), e -> "-".repeat(e))));
        println("超过键名列表的元素会被剔除", Stream.of(data).collect(IRecord.rbclc("红橙黄".split(""), e -> "-".repeat(e))));
        println("省略键名列表,默认采用excelname规则进行键名命名", NVec.of(100,i->i+1).collect(IRecord.rbclc()));
    }

}
